package se.joakimback.decorator.renderer;

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import se.joakimback.decorator.model.Layer;

public class Renderer {
	/* Singleton */

	private static Renderer sSINGLETON = new Renderer();

	public static Renderer getSingleton() {
		return sSINGLETON;
	}

	private int mTotalItems;

	private int mItemCounter;

	private boolean isCanceled;

	private final Random mRandom = new Random();

	private List<ProgressListener> mListeners;

	private double random(double pMax) {
		return 2 * (mRandom.nextDouble() - 0.5) * pMax;
	}

	private void renderInstance(Graphics2D g, BufferedImage pSource, double x,
			double y, double pScale, double pRotation) {
		AffineTransform t = new AffineTransform();

		t.translate(x, y);
		t.scale(pScale, pScale);
		t.rotate(pRotation * Math.PI);
		t.translate(-pSource.getWidth() / 2, -pSource.getHeight() / 2);

		g.drawImage(pSource, t, null);
	}

	private void renderLayer(BufferedImage pImage, Layer pLayer) {

		int d = pLayer.getDensity();
		double w = pImage.getWidth();
		double h = pImage.getHeight();
		double sx = w / d;
		double sy = h / d;

		Graphics2D g = (Graphics2D) pImage.getGraphics();

		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BICUBIC);

		for (int iy = 0; iy < d; iy++) {
			for (int ix = 0; ix < d; ix++) {
				double x = (ix + 0.5) * sx;
				double y = (iy + 0.5) * sy;

				x += random(pLayer.getSpread()) * sx;
				y += random(pLayer.getSpread()) * sy;

				double vSize = pLayer.getBaseScale()
						+ random(pLayer.getScaleVariance());
				double vRotation = random(pLayer.getRotationVariance());

				for (int dy = -1; dy <= 1; dy++) {
					for (int dx = -1; dx <= 1; dx++) {
						renderInstance(g, pLayer.getImage(), x + (dx * w), y
								+ (dy * h), vSize, vRotation);
					}
				}
			}

			mItemCounter += d;

			for (ProgressListener vListener : mListeners) {
				vListener.update((float) mItemCounter / mTotalItems);
			}
		}
	}

	private void init() {
		isCanceled = false;
		mTotalItems = 0;
		mItemCounter = 0;
	}

	private void estimateItems(List<Layer> layers) {
		for (Layer vLayer : layers) {
			mTotalItems += vLayer.getRepeat() * vLayer.getDensity()
					* vLayer.getDensity();
		}
	}

	protected Renderer() {
		mListeners = new LinkedList<ProgressListener>();
	}

	synchronized public void render(BufferedImage pImage, List<Layer> layers) {
		init();
		estimateItems(layers);

		for (ProgressListener vListener : mListeners) {
			vListener.start(mTotalItems);
		}

		for (Layer vLayer : layers) {
			for (int i = 0; i < vLayer.getRepeat(); i++) {
				if (isCanceled)
					break;

				renderLayer(pImage, vLayer);

				for (ProgressListener vListener : mListeners) {
					vListener.preview(pImage);
				}
			}
		}

		for (ProgressListener vListener : mListeners) {
			vListener.done(pImage);
		}
	}

	synchronized public void addProgressListener(ProgressListener pListener) {
		mListeners.add(pListener);
	}

	public void cancel() {
		this.isCanceled = true;
	}
}
